package top.dingwen.io.treasure.encrypt.api.filter;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.ServletRequestPathUtils;
import top.dingwen.io.treasure.base.util.spring.SpringUtils;
import top.dingwen.io.treasure.encrypt.api.annotation.ApiEncrypt;
import top.dingwen.io.treasure.encrypt.api.autoconfigure.EncryptApiProperties;
import top.dingwen.io.treasure.encrypt.api.constant.EncryptApiConstant;
import top.dingwen.io.treasure.encrypt.api.exception.EnCryptApiException;
import top.dingwen.io.treasure.encrypt.api.exception.EncryptApiExceptionCode;
import top.dingwen.io.treasure.webplus.constant.WebPlusConstant;
import top.dingwen.io.treasure.webplus.exception.CommonExceptionCode;
import top.dingwen.io.treasure.webplus.result.Result;
import top.dingwen.io.treasure.webplus.util.response.ResponseUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;


/**
 * Crypto 过滤器
 *
 * @author dingwen
 * @since 2024/11/06
 */
@Slf4j
public class CryptoFilter implements Filter {

    /**
     * 加密配置
     */
    private final EncryptApiProperties properties;

    /**
     * 加密处理过滤器
     */
    public CryptoFilter(EncryptApiProperties properties) {
        this.properties = properties;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpServletResponse servletResponse = (HttpServletResponse) response;
        // 获取加密注解
        ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest, servletResponse);
        boolean responseFlag = Objects.nonNull(apiEncrypt) && apiEncrypt.response();
        ServletRequest requestWrapper = null;
        ServletResponse responseWrapper = null;
        EncryptResponseBodyWrapper responseBodyWrapper = null;

        // 是否为 put 或者 post 请求
        if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
            // 是否存在加密标头
            String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
            if (StrUtil.isNotBlank(headerValue)) {
                // 请求解密
                try {
                    requestWrapper = new DecryptRequestBodyWrapper(
                            servletRequest,
                            properties.getPrivateKey(),
                            properties.getHeaderFlag()
                    );
                } catch (Exception e) {
                    log.error("{},{},请求解密错误，消息[{}]", EncryptApiConstant.EN_L_P_S,
                            EncryptApiConstant.EN_L_P_CF, e.getMessage(), e);
                    handleException(servletRequest, servletResponse,
                            new EnCryptApiException(EncryptApiExceptionCode.ILLEGAL_VISIT));
                }
            } else {
                // 接口需要加密,调用方没有提供加密标头
                if (ObjectUtil.isNotNull(apiEncrypt)) {
                    handleException(servletRequest, servletResponse,
                            new EnCryptApiException(EncryptApiExceptionCode.ILLEGAL_VISIT));
                }
            }
        }

        // 判断是否响应加密
        if (responseFlag) {
            try {
                responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
            } catch (Exception e) {
                log.error("{},{},响应加密错误，消息[{}]", EncryptApiConstant.EN_L_P_S, EncryptApiConstant.EN_L_P_CF,
                        e.getMessage(), e);
                handleException(servletRequest, servletResponse,
                        new EnCryptApiException(EncryptApiExceptionCode.ILLEGAL_VISIT));
            }
            responseWrapper = responseBodyWrapper;
        }

        chain.doFilter(
                ObjectUtil.defaultIfNull(requestWrapper, request),
                ObjectUtil.defaultIfNull(responseWrapper, response));

        if (responseFlag) {
            servletResponse.reset();
            // 对原始内容加密
            String encryptContent;
            try {
                encryptContent = responseBodyWrapper.getEncryptContent(
                        servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
                // 对加密后的内容写出
                servletResponse.getWriter().write(encryptContent);
            } catch (Exception e) {
                log.error("{},{},响应加密错误，消息[{}]", EncryptApiConstant.EN_L_P_S, EncryptApiConstant.EN_L_P_CF,
                        e.getMessage(), e);
                handleException(servletRequest, servletResponse,
                        new EnCryptApiException(EncryptApiExceptionCode.ILLEGAL_VISIT));
            }
        }
    }

    /**
     * 处理异常
     *
     * @param servletRequest  请求
     * @param servletResponse 响应
     * @param ex              异常
     */
    private void handleException(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Exception ex) {
        HandlerExceptionResolver exceptionResolver = SpringUtils.getBean(
                EncryptApiConstant.RM_HER,
                HandlerExceptionResolver.class
        );
        exceptionResolver.resolveException(servletRequest, servletResponse, null, ex);
    }

    /**
     * 获取 ApiEncrypt 注解
     *
     * @param servletRequest 请求对象
     * @return {@link ApiEncrypt } 注解
     */
    private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        try {
            RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean(EncryptApiConstant.RM_HM,
                    RequestMappingHandlerMapping.class);
            // 获取注解
            // 在获取 RequestPath 前确保解析
            ServletRequestPathUtils.parseAndCache(servletRequest);
            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
            if (Objects.nonNull(mappingHandler)) {
                Object handler = mappingHandler.getHandler();
                if (ObjectUtil.isNotNull(handler)) {
                    // 从handler获取注解
                    if (handler instanceof HandlerMethod) {
                        return ((HandlerMethod) handler).getMethodAnnotation(ApiEncrypt.class);
                    }
                }
            }
            return null;
        } catch (Exception e) {
            Result<Object> result = Result
                    .builder()
                    .mudule(WebPlusConstant.WEB_LOG_P_S)
                    .success(Boolean.FALSE)
                    .message(e.getMessage())
                    .code(HttpStatus.BAD_REQUEST.value())
                    .exCode(CommonExceptionCode.WEB_PLUS_VIEW.getExCode())
                    .build();
            if(e instanceof HttpRequestMethodNotSupportedException){
                result.setExCode(CommonExceptionCode.HTTP_REQUEST_METHOD_NOT_SUPPORT.getExCode());
                result.setMessage(CommonExceptionCode.HTTP_REQUEST_METHOD_NOT_SUPPORT.getMessage());
                result.setMudule(CommonExceptionCode.HTTP_REQUEST_METHOD_NOT_SUPPORT.getModule());
                result.setCode(HttpStatus.METHOD_NOT_ALLOWED.value());
            }
            ResponseUtils.responseJson(servletResponse,result);
        }
        return null;

    }

    @Override
    public void destroy() {
    }
}
